# -*- coding: utf-8 -*-

import base64
import logging
import os
import io

try:
    import xlrd

    try:
        from xlrd import xlsx
    except ImportError:
        xlsx = None
except ImportError:
    xlrd = xlsx = None

from datetime import datetime
from tempfile import TemporaryFile
import chardet

from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, pycompat

_logger = logging.getLogger(__name__)

# 文件类型字典
FILE_TYPE_DICT = {
    'text/csv': ('csv', True, None),
    'application/vnd.ms-excel': ('xls', xlrd, 'xlrd'),
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ('xlsx', xlsx, 'xlrd >= 1.0.0')
}

# 文件类型及对应的处理工具
EXTENSIONS = {
    '.' + ext: handler
    for mime, (ext, handler, req) in FILE_TYPE_DICT.items()
}

# 初始化csv参数列表
CSV_OPTIONS = {
    'quoting': '"',
    'separator': ',',
    'encoding': 'gb18030',
}


class AccountBankStatementImportExtend(models.TransientModel):
    _inherit = 'account.bank.statement.import'

    header_line = fields.Integer(string=u'表头信息所在行', required=True, default=1)
    amount_index = fields.Char(string=u'金额所在列下标', help=u'封装格式为：列表下标,金额征服号;')
    partner_id_index = fields.Char(string=u'合作伙伴所在列下标')
    name_index = fields.Char(string=u'参考所在列下标')
    bank_statement_id = fields.Many2one('account.bank.statement', string=u'银行对账单')

    @api.constrains('header_line')
    def _check_header_line(self):
        for line in self:
            if line.header_line <= 0:
                raise ValueError(u'表头信息所在行必须大于0')

    @api.model
    def _is_valid_header(self, key):
        u"""
        判断当前title所代表的有效字段
        :param key: 表头字符串
        :return: 有效的title所代表的字段名称或False
        """

        result = {
            u'收入': ('amount', 1.00),
            u'收入金额': ('amount', 1.00),
            u'记账金额(收入)': ('amount', 1.00),
            u'交易金额': ('amount', 1.00),
            u'贷方发生额/元(收入)': ('amount', 1.00),
            u'借方发生额/元(支取)': ('amount', -1.00),
            u'支出': ('amount', -1.00),
            u'支出金额': ('amount', -1.00),
            u'记账金额(支出)': ('amount', -1.00),
            u'对方账户名称': ('partner_id', ),
            u'对方账号名称': ('partner_id', ),
            u'对方户名': ('partner_id', ),
            u'业务摘要': ('name', ),
            u'交易摘要': ('name', ),
            u'摘要': ('name', ),
        }
        return result.get(key, False)

    @api.multi
    def _read_xls(self, options={}):
        u"""
        读取当前Excel数据
        :return: Excel数据行迭代器
        """

        book = xlrd.open_workbook(file_contents=self.data_file, encoding_override='utf-8')
        return self._read_xls_book(book)

    def _read_xls_book(self, book):
        u"""
        读取当前Excel数据
        :return: Excel数据行迭代器
        """

        # 默认读取Excel第一个工作表
        sheet = book.sheet_by_index(0)
        # 读取Excel列表行
        # TODO 判断用户输入的表头信息所在行是否合法？
        for row in pycompat.imap(sheet.row, range(self.header_line - 1, sheet.nrows)):
            values = []
            for cell in row:
                # 获取每列数据，并格式化字符串
                if cell.ctype is xlrd.XL_CELL_NUMBER:
                    is_float = cell.value % 1 != 0.0
                    values.append(
                        pycompat.text_type(cell.value)
                        if is_float
                        else pycompat.text_type(int(cell.value))
                    )
                elif cell.ctype is xlrd.XL_CELL_DATE:
                    is_datetime = cell.value % 1 != 0.0
                    # emulate xldate_as_datetime for pre-0.9.3
                    dt = datetime.datetime(*xlrd.xldate.xldate_as_tuple(cell.value, book.datemode))
                    values.append(
                        dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
                        if is_datetime
                        else dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
                    )
                elif cell.ctype is xlrd.XL_CELL_BOOLEAN:
                    values.append(u'True' if cell.value else u'False')
                elif cell.ctype is xlrd.XL_CELL_ERROR:
                    raise ValueError(
                        _("Error cell found while reading XLS/XLSX file: %s") %
                        xlrd.error_text_from_code.get(
                            cell.value, "unknown error code %s" % cell.value)
                    )
                else:
                    values.append(cell.value)
            if any(x for x in values if x.strip()):
                yield values

    @api.multi
    def _read_csv(self, options):
        """ Returns a CSV-parsed iterator of all empty lines in the file
            :throws csv.Error: if an error is detected during CSV parsing
            :throws UnicodeDecodeError: if ``options.encoding`` is incorrect
        """
        csv_data = self.data_file
        # 获取编码，并格式化字符串
        encoding = options.get('encoding', 'utf-8')
        if encoding != 'utf-8':
            # csv module expect utf-8, see http://docs.python.org/2/library/csv.html
            try:
                csv_data = csv_data.decode(encoding).encode('utf-8')
            except:
                # 判断数据的编码，并重新格式化字符串，该功能主要针对对私中行.csv的导入
                encoding = chardet.detect(csv_data).get('encoding')
                csv_data = csv_data.decode(encoding).encode('utf-8')
                options.update({
                    'quoting': '"',
                    'separator': '\t',
                })
        csv_iterator = pycompat.csv_reader(
            io.BytesIO(csv_data),
            quotechar=str(options['quoting']),
            delimiter=str(options['separator']))
        return (
            row for row in csv_iterator
            if any(x for x in row if x.strip())
        )

    def _create_bank_payment_line(self, data, rows=None):
        u"""
        根据数据行，获取表头有效字段所对应的下标，并根据下标，获取每个数据行对应的数据
        :param data: 表头文字信息列表 或 每个银行对账单明细表信息
        :param rows: 数据对应的行数，用于提示用户错误所在的地方(从0开始的，所以需要加1以匹配正常的阅读习惯)
        :return: None 或 创建的对账单明细
        """

        # 如果当前title信息
        # TODO 如果输入表头信息所在行，不正确，提示用户重写填写？还是自动获取
        if not self.name_index:
            for line in data:
                header = self._is_valid_header(line.replace(' ', ''))
                if header:
                    if header[0] == 'amount':
                        # 收入支出金额所在的数据列下标
                        self.amount_index = (self.amount_index or '') + str(data.index(line)) + ',' + str(header[-1]) + ';'
                    elif header[0] == 'name':
                        # 摘要所在的数据列下标
                        self.name_index = str(data.index(line))
                    else:
                        # 合作伙伴所在的数据列下标
                        self.partner_id_index = str(data.index(line))
            return
        # 获取各个列所在下标并转化为有效整数
        name_index = int(self.name_index)
        name = data[name_index] or u'无'
        partner_id_index = int(self.partner_id_index) if self.partner_id_index else -1
        # 获取合作伙伴id
        partner_obj = self.env['res.partner']
        # 初始化交易类型
        statement_calss_obj = self.env.ref('account_extend.account_statement_line_class_3')

        # 获取交易日期 （李黎添加，强制取第一列的数据，也就是下标0）
        date = data[0].strip()
        try:
            date = datetime.strptime(date, '%Y%m%d').date()
        except ValueError:
            raise UserError('表格的第一列的时间格式不正确，请确保是类似于“20180522”形式的时间')

        # 如果当前任一有效列下标大于了整个数据行所包含的数据长度，那么说明有效数据已经处理完成
        if len(data) - 1 < name_index or len(data) - 1 < partner_id_index:
            return

        # 获取金额
        amount_index = [r.split(',') for r in self.amount_index.split(';')]
        amount_index.pop()
        amount = 0.0
        for a_index in amount_index:
            if int(a_index[0]) > len(data) - 1:
                return
            # 金额数据行中，有多种格式，但是都是以字符串存储的，所以需要先替换字符串，使其成为有效的金额
            money = data[int(a_index[0])].replace(' ', '').replace(',', '').replace('\t', '').replace('--', '')
            amount += (float(money or 0.0) * float(a_index[1]))

        if partner_id_index >= 0:
            partner_type = 'customer' if amount > 0 else 'supplier'
            partner_obj = partner_obj.search([('name', '=', data[partner_id_index].replace(' ', '')), (partner_type, '=', True)])
            if len(partner_obj) > 0:
                if partner_obj[-1].supplier is True:
                    statement_calss_obj = self.env.ref('account_extend.account_statement_line_class_4')

        # 检查是否有重复项
        origin_record = data[partner_id_index].replace(' ', '')
        # duplicates = self._is_duplicated(str(date), origin_record, amount)
        # if duplicates:
        #     msg = '发现重复项，在导入文件的第%s行，\n' \
        #           '重复项在日期为：“%s”， \n' \
        #           '日记账为：“%s“ 的银行对账单中，\n' \
        #           '对方账号名称为 ”%s“' % \
        #           (rows+1, duplicates[0].date ,duplicates[0].name, origin_record)
        #     raise UserError(msg)

        # 创建对账单明细
        res = self.env['account.bank.statement.line'].create({
            'date': date,
            'statement_id': self.bank_statement_id.id,
            'statement_class': statement_calss_obj.id,
            'amount': amount,
            'name': name,
            'partner_id': partner_obj[-1].id if partner_obj else None,
            'bank_note': data[partner_id_index] if partner_id_index >= 0 and not partner_obj else '',
            'origin_record': origin_record,
        })
        res._onchange_xdl_account_id()  # 手动调用方法，带出相关科目
        return res

    def _is_duplicated(self, date, name, amount):
        # 根据交易时间、对方账号名称、交易金额，判断是否重复导入
        # :param date: str,交易日期
        # :param name: str,对方账号名称
        # :param amount: float,交易金额
        # :return: 'account.bank.statement', 搜索到的重复项所在的的银行对账单

        journal_id = self.bank_statement_id.journal_id
        statement_ids = self.env['account.bank.statement'].search([('journal_id', '=', journal_id.id)])
        # 正在导入的这个文件里可能存在重复项，这种应该跳过，也就是不会搜索刚创建的银行对账单
        statement_ids -= self.bank_statement_id
        statement_lines = statement_ids.mapped('line_ids')
        duplicated = lambda x: x.date == date and x.origin_record == name and x.amount == amount
        filtered_lines = statement_lines.filtered(duplicated)
        if filtered_lines:
            return filtered_lines.mapped('statement_id')

    @api.multi
    def import_file(self):
        # 确保单例操作
        self.ensure_one()
        file_data = []
        if self.filename:
            # 根据文件名称获取文件类型和对账单名称
            p, ext = os.path.splitext(self.filename)
            # 获取银行日记账并创建银行对账单
            journal_id = self.env['account.journal'].search([('id', '=', int(self._context.get('journal_id', 0)))])
            self.bank_statement_id = self.env['account.bank.statement'].create({
                'name': p,
                'date': datetime.now(),
                'journal_id': journal_id[-1].id if len(journal_id) > 0 else None
            }).id
            if ext in EXTENSIONS:
                try:
                    file_data = getattr(self, '_read_' + ext[1:])(CSV_OPTIONS)
                except Exception as e:
                    _logger.error(e)
                    raise UserError(u"系统错误，请联系系统管理员！")
            if file_data:
                for rows, record in enumerate(file_data):
                    self._create_bank_payment_line(record, rows)
        else:
            return

    @api.model
    def create(self, vals):
        # odoo获取了文件数据后，默认会转换为base64格式，而数据处理中的csv方法和Excel方法都需要原格式的数据，所以在创建记录时，
        # 需要转换文件的数据
        if vals.get('data_file'):
            vals.update({
                'data_file': base64.b64decode(vals.get('data_file'))
            })
        return super(AccountBankStatementImportExtend, self).create(vals)

